/// Translated from C to D
module glfw3.wl_window;

version(Windows):
@nogc nothrow:
extern(C): __gshared:


//========================================================================
// GLFW 3.3 Wayland - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2014 Jonas Ådahl <jadahl@gmail.com>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
//========================================================================
// It is fine to use C99 in this file because it will not be built with VS
//========================================================================

public import glfw3.internal;

public import core.stdc.stdio;
public import core.stdc.stdlib;
public import core.stdc.errno;
public import core.sys.posix.unistd;
public import core.stdc.string;
public import core.sys.posix.fcntl;
public import core.sys.posix.sys.mman;
public import core.sys.linux.timerfd;
public import core.sys.posix.poll;

private void shellSurfaceHandlePing(void* data, wl_shell_surface* shellSurface, uint serial) {
    wl_shell_surface_pong(shellSurface, serial);
}

private void shellSurfaceHandleConfigure(void* data, wl_shell_surface* shellSurface, uint edges, int width, int height) {
    auto window = cast(_GLFWwindow*) data;
    float aspectRatio;
    float targetRatio;

    if (!window.monitor)
    {
        if (_glfw.wl.viewporter && window.decorated)
        {
            width -= _GLFW_DECORATION_HORIZONTAL;
            height -= _GLFW_DECORATION_VERTICAL;
        }
        if (width < 1)
            width = 1;
        if (height < 1)
            height = 1;

        if (window.numer != GLFW_DONT_CARE && window.denom != GLFW_DONT_CARE)
        {
            aspectRatio = cast(float)width / cast(float)height;
            targetRatio = cast(float)window.numer / cast(float)window.denom;
            if (aspectRatio < targetRatio)
                height = cast(int) (width / targetRatio);
            else if (aspectRatio > targetRatio)
                width = cast(int) (height * targetRatio);
        }

        if (window.minwidth != GLFW_DONT_CARE && width < window.minwidth)
            width = window.minwidth;
        else if (window.maxwidth != GLFW_DONT_CARE && width > window.maxwidth)
            width = window.maxwidth;

        if (window.minheight != GLFW_DONT_CARE && height < window.minheight)
            height = window.minheight;
        else if (window.maxheight != GLFW_DONT_CARE && height > window.maxheight)
            height = window.maxheight;
    }

    _glfwInputWindowSize(window, width, height);
    _glfwPlatformSetWindowSize(window, width, height);
    _glfwInputWindowDamage(window);
}

private void shellSurfaceHandlePopupDone(void* data, wl_shell_surface* shellSurface) {
}

private const(wl_shell_surface_listener) shellSurfaceListener = wl_shell_surface_listener(
    &shellSurfaceHandlePing,
    &shellSurfaceHandleConfigure,
    &shellSurfaceHandlePopupDone
);

private int createTmpfileCloexec(char* tmpname) {
    int fd;

    fd = mkostemp(tmpname, O_CLOEXEC);
    if (fd >= 0)
        unlink(tmpname);

    return fd;
}

/*
 * Create a new, unique, anonymous file of the given size, and
 * return the file descriptor for it. The file descriptor is set
 * CLOEXEC. The file is immediately suitable for mmap()'ing
 * the given size at offset zero.
 *
 * The file should not have a permanent backing store like a disk,
 * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
 *
 * The file name is deleted from the file system.
 *
 * The file is suitable for buffer sharing between processes by
 * transmitting the file descriptor over Unix sockets using the
 * SCM_RIGHTS methods.
 *
 * posix_fallocate() is used to guarantee that disk space is available
 * for the file at the given size. If disk space is insufficient, errno
 * is set to ENOSPC. If posix_fallocate() is not supported, program may
 * receive SIGBUS on accessing mmap()'ed file contents instead.
 */
private int createAnonymousFile(off_t size) {
    const(char)* template_ = "/glfw-shared-XXXXXX";
    const(char)* path;
    char* name;
    int fd;
    int ret;

    auto inner() {
        path = getenv("XDG_RUNTIME_DIR");
        if (!path)
        {
            errno = ENOENT;
            return -1;
        }

        name = cast(char*) calloc(strlen(path) + template_.sizeof, 1);
        strcpy(name, path);
        strcat(name, template_);

        fd = createTmpfileCloexec(name);
        free(name);
        if (fd < 0)
            return -1;

        return 0;
    }

version(HAVE_MEMFD_CREATE) {
    fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
    if (fd >= 0)
    {
        // We can add this seal before calling posix_fallocate(), as the file
        // is currently zero-sized anyway.
        //
        // There is also no need to check for the return value, we couldn’t do
        // anything with it anyway.
        fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
    }
    else
    {
        if (inner() < 0) {
            return -1;
        }
    }
} else version(SHM_ANON) {
    fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0x180); // 0x180 = octal!600
    if (fd < 0) {
        if (inner() < 0) {
            return -1;
        }
    }
}

version(SHM_ANON) {
    // posix_fallocate does not work on SHM descriptors
    ret = ftruncate(fd, size);
} else {
    ret = posix_fallocate(fd, 0, size);
}
    if (ret != 0)
    {
        close(fd);
        errno = ret;
        return -1;
    }
    return fd;
}

private wl_buffer* createShmBuffer(const(GLFWimage)* image) {
    wl_shm_pool* pool;
    wl_buffer* buffer;
    int stride = image.width * 4;
    int length = image.width * image.height * 4;
    void* data;
    int fd;int i;

    fd = createAnonymousFile(length);
    if (fd < 0)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Creating a buffer file for %d B failed: %m",
                        length);
        return null;
    }

    data = mmap(null, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (data == MAP_FAILED)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: mmap failed: %m");
        close(fd);
        return null;
    }

    pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);

    close(fd);
    ubyte* source = cast(ubyte*) image.pixels;
    ubyte* target = data;
    for (i = 0;  i < image.width * image.height;  i++, source += 4)
    {
        uint alpha = source[3];

        *target++ = cast(ubyte) ((source[2] * alpha) / 255);
        *target++ = cast(ubyte) ((source[1] * alpha) / 255);
        *target++ = cast(ubyte) ((source[0] * alpha) / 255);
        *target++ = cast(ubyte) alpha;
    }

    buffer =
        wl_shm_pool_create_buffer(pool, 0,
                                  image.width,
                                  image.height,
                                  stride, WL_SHM_FORMAT_ARGB8888);
    munmap(data, length);
    wl_shm_pool_destroy(pool);

    return buffer;
}

private void createDecoration(_GLFWdecorationWayland* decoration, wl_surface* parent, wl_buffer* buffer, GLFWbool opaque, int x, int y, int width, int height) {
    wl_region* region;

    decoration.surface = wl_compositor_create_surface(_glfw.wl.compositor);
    decoration.subsurface =
        wl_subcompositor_get_subsurface(_glfw.wl.subcompositor,
                                        decoration.surface, parent);
    wl_subsurface_set_position(decoration.subsurface, x, y);
    decoration.viewport = wp_viewporter_get_viewport(_glfw.wl.viewporter,
                                                      decoration.surface);
    wp_viewport_set_destination(decoration.viewport, width, height);
    wl_surface_attach(decoration.surface, buffer, 0, 0);

    if (opaque)
    {
        region = wl_compositor_create_region(_glfw.wl.compositor);
        wl_region_add(region, 0, 0, width, height);
        wl_surface_set_opaque_region(decoration.surface, region);
        wl_surface_commit(decoration.surface);
        wl_region_destroy(region);
    }
    else
        wl_surface_commit(decoration.surface);
}

private void createDecorations(_GLFWwindow* window) {
    ubyte[4] data = [ 224, 224, 224, 255 ];
    const(GLFWimage) image = GLFWimage( 1, 1, data.ptr );
    GLFWbool opaque = (data[3] == 255);

    if (!_glfw.wl.viewporter || !window.decorated || window.wl.decorations.serverSide)
        return;

    if (!window.wl.decorations.buffer)
        window.wl.decorations.buffer = createShmBuffer(&image);
    if (!window.wl.decorations.buffer)
        return;

    createDecoration(&window.wl.decorations.top, window.wl.surface,
                     window.wl.decorations.buffer, opaque,
                     0, -_GLFW_DECORATION_TOP,
                     window.wl.width, _GLFW_DECORATION_TOP);
    createDecoration(&window.wl.decorations.left, window.wl.surface,
                     window.wl.decorations.buffer, opaque,
                     -_GLFW_DECORATION_WIDTH, -_GLFW_DECORATION_TOP,
                     _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
    createDecoration(&window.wl.decorations.right, window.wl.surface,
                     window.wl.decorations.buffer, opaque,
                     window.wl.width, -_GLFW_DECORATION_TOP,
                     _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
    createDecoration(&window.wl.decorations.bottom, window.wl.surface,
                     window.wl.decorations.buffer, opaque,
                     -_GLFW_DECORATION_WIDTH, window.wl.height,
                     window.wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
}

private void destroyDecoration(_GLFWdecorationWayland* decoration) {
    if (decoration.surface)
        wl_surface_destroy(decoration.surface);
    if (decoration.subsurface)
        wl_subsurface_destroy(decoration.subsurface);
    if (decoration.viewport)
        wp_viewport_destroy(decoration.viewport);
    decoration.surface = null;
    decoration.subsurface = null;
    decoration.viewport = null;
}

private void destroyDecorations(_GLFWwindow* window) {
    destroyDecoration(&window.wl.decorations.top);
    destroyDecoration(&window.wl.decorations.left);
    destroyDecoration(&window.wl.decorations.right);
    destroyDecoration(&window.wl.decorations.bottom);
}

private void xdgDecorationHandleConfigure(void* data, zxdg_toplevel_decoration_v1* decoration, uint mode) {
    _GLFWwindow* window = data;

    window.wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);

    if (!window.wl.decorations.serverSide)
        createDecorations(window);
}

private const(zxdg_toplevel_decoration_v1_listener) xdgDecorationListener = zxdg_toplevel_decoration_v1_listener(
    &xdgDecorationHandleConfigure,
);

// Makes the surface considered as XRGB instead of ARGB.
private void setOpaqueRegion(_GLFWwindow* window) {
    wl_region* region;

    region = wl_compositor_create_region(_glfw.wl.compositor);
    if (!region)
        return;

    wl_region_add(region, 0, 0, window.wl.width, window.wl.height);
    wl_surface_set_opaque_region(window.wl.surface, region);
    wl_surface_commit(window.wl.surface);
    wl_region_destroy(region);
}


private void resizeWindow(_GLFWwindow* window) {
    int scale = window.wl.scale;
    int scaledWidth = window.wl.width * scale;
    int scaledHeight = window.wl.height * scale;
    wl_egl_window_resize(window.wl.native, scaledWidth, scaledHeight, 0, 0);
    if (!window.wl.transparent)
        setOpaqueRegion(window);
    _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
    _glfwInputWindowContentScale(window, scale, scale);

    if (!window.wl.decorations.top.surface)
        return;

    // Top decoration.
    wp_viewport_set_destination(window.wl.decorations.top.viewport,
                                window.wl.width, _GLFW_DECORATION_TOP);
    wl_surface_commit(window.wl.decorations.top.surface);

    // Left decoration.
    wp_viewport_set_destination(window.wl.decorations.left.viewport,
                                _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
    wl_surface_commit(window.wl.decorations.left.surface);

    // Right decoration.
    wl_subsurface_set_position(window.wl.decorations.right.subsurface,
                               window.wl.width, -_GLFW_DECORATION_TOP);
    wp_viewport_set_destination(window.wl.decorations.right.viewport,
                                _GLFW_DECORATION_WIDTH, window.wl.height + _GLFW_DECORATION_TOP);
    wl_surface_commit(window.wl.decorations.right.surface);

    // Bottom decoration.
    wl_subsurface_set_position(window.wl.decorations.bottom.subsurface,
                               -_GLFW_DECORATION_WIDTH, window.wl.height);
    wp_viewport_set_destination(window.wl.decorations.bottom.viewport,
                                window.wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
    wl_surface_commit(window.wl.decorations.bottom.surface);
}

private void checkScaleChange(_GLFWwindow* window) {
    int scale = 1;
    int i;
    int monitorScale;

    // Check if we will be able to set the buffer scale or not.
    if (_glfw.wl.compositorVersion < 3)
        return;

    // Get the scale factor from the highest scale monitor.
    for (i = 0; i < window.wl.monitorsCount; ++i)
    {
        monitorScale = window.wl.monitors[i].wl.scale;
        if (scale < monitorScale)
            scale = monitorScale;
    }

    // Only change the framebuffer size if the scale changed.
    if (scale != window.wl.scale)
    {
        window.wl.scale = scale;
        wl_surface_set_buffer_scale(window.wl.surface, scale);
        resizeWindow(window);
    }
}

private void surfaceHandleEnter(void* data, wl_surface* surface, wl_output* output) {
    _GLFWwindow* window = data;
    _GLFWmonitor* monitor = wl_output_get_user_data(output);

    if (window.wl.monitorsCount + 1 > window.wl.monitorsSize)
    {
        ++window.wl.monitorsSize;
        window.wl.monitors =
            realloc(window.wl.monitors,
                    window.wl.monitorsSize * (_GLFWmonitor*).sizeof);
    }

    window.wl.monitors[window.wl.monitorsCount++] = monitor;

    checkScaleChange(window);
}

private void surfaceHandleLeave(void* data, wl_surface* surface, wl_output* output) {
    _GLFWwindow* window = data;
    _GLFWmonitor* monitor = wl_output_get_user_data(output);
    GLFWbool found;
    int i;

    for (i = 0, found = GLFW_FALSE; i < window.wl.monitorsCount - 1; ++i)
    {
        if (monitor == window.wl.monitors[i])
            found = GLFW_TRUE;
        if (found)
            window.wl.monitors[i] = window.wl.monitors[i + 1];
    }
    window.wl.monitors[--window.wl.monitorsCount] = null;

    checkScaleChange(window);
}

private const(wl_surface_listener) surfaceListener = wl_surface_listener(
    &surfaceHandleEnter,
    &surfaceHandleLeave
);

private void setIdleInhibitor(_GLFWwindow* window, GLFWbool enable) {
    if (enable && !window.wl.idleInhibitor && _glfw.wl.idleInhibitManager)
    {
        window.wl.idleInhibitor =
            zwp_idle_inhibit_manager_v1_create_inhibitor(
                _glfw.wl.idleInhibitManager, window.wl.surface);
        if (!window.wl.idleInhibitor)
            _glfwInputError(GLFW_PLATFORM_ERROR,
                            "Wayland: Idle inhibitor creation failed");
    }
    else if (!enable && window.wl.idleInhibitor)
    {
        zwp_idle_inhibitor_v1_destroy(window.wl.idleInhibitor);
        window.wl.idleInhibitor = null;
    }
}

private GLFWbool createSurface(_GLFWwindow* window, const(_GLFWwndconfig)* wndconfig) {
    window.wl.surface = wl_compositor_create_surface(_glfw.wl.compositor);
    if (!window.wl.surface)
        return GLFW_FALSE;

    wl_surface_add_listener(window.wl.surface,
                            &surfaceListener,
                            window);

    wl_surface_set_user_data(window.wl.surface, window);

    window.wl.native = wl_egl_window_create(window.wl.surface,
                                             wndconfig.width,
                                             wndconfig.height);
    if (!window.wl.native)
        return GLFW_FALSE;

    window.wl.width = wndconfig.width;
    window.wl.height = wndconfig.height;
    window.wl.scale = 1;

    if (!window.wl.transparent)
        setOpaqueRegion(window);

    return GLFW_TRUE;
}

private void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refreshRate) {
    if (window.wl.xdg.toplevel)
    {
        xdg_toplevel_set_fullscreen(
            window.wl.xdg.toplevel,
            monitor.wl.output);
    }
    else if (window.wl.shellSurface)
    {
        wl_shell_surface_set_fullscreen(
            window.wl.shellSurface,
            WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
            refreshRate * 1000, // Convert Hz to mHz.
            monitor.wl.output);
    }
    setIdleInhibitor(window, GLFW_TRUE);
    if (!window.wl.decorations.serverSide)
        destroyDecorations(window);
}

private GLFWbool createShellSurface(_GLFWwindow* window) {
    if (!_glfw.wl.shell)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: wl_shell protocol not available");
        return GLFW_FALSE;
    }

    window.wl.shellSurface = wl_shell_get_shell_surface(_glfw.wl.shell,
                                                         window.wl.surface);
    if (!window.wl.shellSurface)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Shell surface creation failed");
        return GLFW_FALSE;
    }

    wl_shell_surface_add_listener(window.wl.shellSurface,
                                  &shellSurfaceListener,
                                  window);

    if (window.wl.title)
        wl_shell_surface_set_title(window.wl.shellSurface, window.wl.title);

    if (window.monitor)
    {
        setFullscreen(window, window.monitor, 0);
    }
    else if (window.wl.maximized)
    {
        wl_shell_surface_set_maximized(window.wl.shellSurface, null);
        setIdleInhibitor(window, GLFW_FALSE);
        createDecorations(window);
    }
    else
    {
        wl_shell_surface_set_toplevel(window.wl.shellSurface);
        setIdleInhibitor(window, GLFW_FALSE);
        createDecorations(window);
    }

    wl_surface_commit(window.wl.surface);

    return GLFW_TRUE;
}

private void xdgToplevelHandleConfigure(void* data, xdg_toplevel* toplevel, int width, int height, wl_array* states) {
    _GLFWwindow* window = cast(_GLFWwindow*) data;
    float aspectRatio;
    float targetRatio;
    uint* state;
    GLFWbool maximized = GLFW_FALSE;
    GLFWbool fullscreen = GLFW_FALSE;
    GLFWbool activated = GLFW_FALSE;

    foreach(state; wl_array_for_each(states))
    {
        switch (*state)
        {
            case XDG_TOPLEVEL_STATE_MAXIMIZED:
                maximized = GLFW_TRUE;
                break;
            case XDG_TOPLEVEL_STATE_FULLSCREEN:
                fullscreen = GLFW_TRUE;
                break;
            case XDG_TOPLEVEL_STATE_RESIZING:
                break;
            case XDG_TOPLEVEL_STATE_ACTIVATED:
                activated = GLFW_TRUE;
                break;
        }
    }

    if (width != 0 && height != 0)
    {
        if (!maximized && !fullscreen)
        {
            if (window.numer != GLFW_DONT_CARE && window.denom != GLFW_DONT_CARE)
            {
                aspectRatio = cast(float)width / cast(float)height;
                targetRatio = cast(float)window.numer / cast(float)window.denom;
                if (aspectRatio < targetRatio)
                    height = width / targetRatio;
                else if (aspectRatio > targetRatio)
                    width = height * targetRatio;
            }
        }

        _glfwInputWindowSize(window, width, height);
        _glfwPlatformSetWindowSize(window, width, height);
        _glfwInputWindowDamage(window);
    }

    if (window.wl.wasFullscreen && window.autoIconify)
    {
        if (!activated || !fullscreen)
        {
            _glfwPlatformIconifyWindow(window);
            window.wl.wasFullscreen = GLFW_FALSE;
        }
    }
    if (fullscreen && activated)
        window.wl.wasFullscreen = GLFW_TRUE;
    _glfwInputWindowFocus(window, activated);
}

private void xdgToplevelHandleClose(void* data, xdg_toplevel* toplevel) {
    _GLFWwindow* window = data;
    _glfwInputWindowCloseRequest(window);
}

private const(xdg_toplevel_listener) xdgToplevelListener = xdg_toplevel_listener(
    &xdgToplevelHandleConfigure,
    &xdgToplevelHandleClose
);

private void xdgSurfaceHandleConfigure(void* data, xdg_surface* surface, uint serial) {
    xdg_surface_ack_configure(surface, serial);
}

private const(xdg_surface_listener) xdgSurfaceListener = xdg_surface_listener(
    &xdgSurfaceHandleConfigure
);

private void setXdgDecorations(_GLFWwindow* window) {
    if (_glfw.wl.decorationManager)
    {
        window.wl.xdg.decoration =
            zxdg_decoration_manager_v1_get_toplevel_decoration(
                _glfw.wl.decorationManager, window.wl.xdg.toplevel);
        zxdg_toplevel_decoration_v1_add_listener(window.wl.xdg.decoration,
                                                 &xdgDecorationListener,
                                                 window);
        zxdg_toplevel_decoration_v1_set_mode(
            window.wl.xdg.decoration,
            ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
    }
    else
    {
        window.wl.decorations.serverSide = GLFW_FALSE;
        createDecorations(window);
    }
}

private GLFWbool createXdgSurface(_GLFWwindow* window) {
    window.wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
                                                         window.wl.surface);
    if (!window.wl.xdg.surface)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: xdg-surface creation failed");
        return GLFW_FALSE;
    }

    xdg_surface_add_listener(window.wl.xdg.surface,
                             &xdgSurfaceListener,
                             window);

    window.wl.xdg.toplevel = xdg_surface_get_toplevel(window.wl.xdg.surface);
    if (!window.wl.xdg.toplevel)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: xdg-toplevel creation failed");
        return GLFW_FALSE;
    }

    xdg_toplevel_add_listener(window.wl.xdg.toplevel,
                              &xdgToplevelListener,
                              window);

    if (window.wl.title)
        xdg_toplevel_set_title(window.wl.xdg.toplevel, window.wl.title);

    if (window.minwidth != GLFW_DONT_CARE && window.minheight != GLFW_DONT_CARE)
        xdg_toplevel_set_min_size(window.wl.xdg.toplevel,
                                  window.minwidth, window.minheight);
    if (window.maxwidth != GLFW_DONT_CARE && window.maxheight != GLFW_DONT_CARE)
        xdg_toplevel_set_max_size(window.wl.xdg.toplevel,
                                  window.maxwidth, window.maxheight);

    if (window.monitor)
    {
        xdg_toplevel_set_fullscreen(window.wl.xdg.toplevel,
                                    window.monitor.wl.output);
        setIdleInhibitor(window, GLFW_TRUE);
    }
    else if (window.wl.maximized)
    {
        xdg_toplevel_set_maximized(window.wl.xdg.toplevel);
        setIdleInhibitor(window, GLFW_FALSE);
        setXdgDecorations(window);
    }
    else
    {
        setIdleInhibitor(window, GLFW_FALSE);
        setXdgDecorations(window);
    }

    wl_surface_commit(window.wl.surface);
    wl_display_roundtrip(_glfw.wl.display);

    return GLFW_TRUE;
}

private void setCursorImage(_GLFWwindow* window, _GLFWcursorWayland* cursorWayland) {
    itimerspec timer = {};
    wl_cursor* wlCursor = cursorWayland.cursor;
    wl_cursor_image* image;
    wl_buffer* buffer;
    wl_surface* surface = _glfw.wl.cursorSurface;
    int scale = 1;

    if (!wlCursor)
        buffer = cursorWayland.buffer;
    else
    {
        if (window.wl.scale > 1 && cursorWayland.cursorHiDPI)
        {
            wlCursor = cursorWayland.cursorHiDPI;
            scale = 2;
        }

        image = wlCursor.images[cursorWayland.currentImage];
        buffer = _glfw.wl.cursor.image_get_buffer(image);
        if (!buffer)
            return;

        timer.it_value.tv_sec = image.delay / 1000;
        timer.it_value.tv_nsec = (image.delay % 1000) * 1000000;
        timerfd_settime(_glfw.wl.cursorTimerfd, 0, &timer, null);

        cursorWayland.width = image.width;
        cursorWayland.height = image.height;
        cursorWayland.xhot = image.hotspot_x;
        cursorWayland.yhot = image.hotspot_y;
    }

    wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
                          surface,
                          cursorWayland.xhot / scale,
                          cursorWayland.yhot / scale);
    wl_surface_set_buffer_scale(surface, scale);
    wl_surface_attach(surface, buffer, 0, 0);
    wl_surface_damage(surface, 0, 0,
                      cursorWayland.width, cursorWayland.height);
    wl_surface_commit(surface);
}

private void incrementCursorImage(_GLFWwindow* window) {
    _GLFWcursor* cursor;

    if (!window || window.wl.decorations.focus != mainWindow)
        return;

    cursor = window.wl.currentCursor;
    if (cursor && cursor.wl.cursor)
    {
        cursor.wl.currentImage += 1;
        cursor.wl.currentImage %= cursor.wl.cursor.image_count;
        setCursorImage(window, &cursor.wl);
    }
}

private void handleEvents(int timeout) {
    wl_display* display = _glfw.wl.display;
    pollfd* fds = [
        [ wl_display_get_fd(display), POLLIN ],
        [ _glfw.wl.timerfd, POLLIN ],
        [ _glfw.wl.cursorTimerfd, POLLIN ],
    ];
    ssize_t read_ret;
    ulong repeats;ulong i;

    while (wl_display_prepare_read(display) != 0)
        wl_display_dispatch_pending(display);

    // If an error different from EAGAIN happens, we have likely been
    // disconnected from the Wayland session, try to handle that the best we
    // can.
    if (wl_display_flush(display) < 0 && errno != EAGAIN)
    {
        _GLFWwindow* window = _glfw.windowListHead;
        while (window)
        {
            _glfwInputWindowCloseRequest(window);
            window = window.next;
        }
        wl_display_cancel_read(display);
        return;
    }

    if (poll(fds, 3, timeout) > 0)
    {
        if (fds[0].revents & POLLIN)
        {
            wl_display_read_events(display);
            wl_display_dispatch_pending(display);
        }
        else
        {
            wl_display_cancel_read(display);
        }

        if (fds[1].revents & POLLIN)
        {
            read_ret = read(_glfw.wl.timerfd, &repeats, repeats.sizeof);
            if (read_ret != 8)
                return;

            for (i = 0; i < repeats; ++i)
                _glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey,
                              _glfw.wl.keyboardLastScancode, GLFW_REPEAT,
                              _glfw.wl.xkb.modifiers);
        }

        if (fds[2].revents & POLLIN)
        {
            read_ret = read(_glfw.wl.cursorTimerfd, &repeats, repeats.sizeof);
            if (read_ret != 8)
                return;

            incrementCursorImage(_glfw.wl.pointerFocus);
        }
    }
    else
    {
        wl_display_cancel_read(display);
    }
}

// Translates a GLFW standard cursor to a theme cursor name
//
private const(char)* translateCursorShape(int shape) {
    switch (shape)
    {
        case GLFW_ARROW_CURSOR:
            return "left_ptr".ptr;
        case GLFW_IBEAM_CURSOR:
            return "xterm".ptr;
        case GLFW_CROSSHAIR_CURSOR:
            return "crosshair".ptr;
        case GLFW_HAND_CURSOR:
            return "hand2".ptr;
        case GLFW_HRESIZE_CURSOR:
            return "sb_h_double_arrow".ptr;
        case GLFW_VRESIZE_CURSOR:
            return "sb_v_double_arrow".ptr;
    }
    return null;
}

//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

int _glfwPlatformCreateWindow(_GLFWwindow* window, const(_GLFWwndconfig)* wndconfig, const(_GLFWctxconfig)* ctxconfig, const(_GLFWfbconfig)* fbconfig) {
    window.wl.transparent = fbconfig.transparent;

    if (!createSurface(window, wndconfig))
        return GLFW_FALSE;

    if (ctxconfig.client != GLFW_NO_API)
    {
        if (ctxconfig.source == GLFW_EGL_CONTEXT_API ||
            ctxconfig.source == GLFW_NATIVE_CONTEXT_API)
        {
            if (!_glfwInitEGL())
                return GLFW_FALSE;
            if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
        }
        else if (ctxconfig.source == GLFW_OSMESA_CONTEXT_API)
        {
            if (!_glfwInitOSMesa())
                return GLFW_FALSE;
            if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
        }
    }

    if (wndconfig.title)
        window.wl.title = _glfw_strdup(wndconfig.title);

    if (wndconfig.visible)
    {
        if (_glfw.wl.wmBase)
        {
            if (!createXdgSurface(window))
                return GLFW_FALSE;
        }
        else
        {
            if (!createShellSurface(window))
                return GLFW_FALSE;
        }

        window.wl.visible = GLFW_TRUE;
    }
    else
    {
        window.wl.xdg.surface = null;
        window.wl.xdg.toplevel = null;
        window.wl.shellSurface = null;
        window.wl.visible = GLFW_FALSE;
    }

    window.wl.currentCursor = null;

    window.wl.monitors = cast(_GLFWmonitor**) calloc(1, (_GLFWmonitor*).sizeof);
    window.wl.monitorsCount = 0;
    window.wl.monitorsSize = 1;

    return GLFW_TRUE;
}

void _glfwPlatformDestroyWindow(_GLFWwindow* window) {
    if (window == _glfw.wl.pointerFocus)
    {
        _glfw.wl.pointerFocus = null;
        _glfwInputCursorEnter(window, GLFW_FALSE);
    }
    if (window == _glfw.wl.keyboardFocus)
    {
        _glfw.wl.keyboardFocus = null;
        _glfwInputWindowFocus(window, GLFW_FALSE);
    }

    if (window.wl.idleInhibitor)
        zwp_idle_inhibitor_v1_destroy(window.wl.idleInhibitor);

    if (window.context.destroy)
        window.context.destroy(window);

    destroyDecorations(window);
    if (window.wl.xdg.decoration)
        zxdg_toplevel_decoration_v1_destroy(window.wl.xdg.decoration);

    if (window.wl.decorations.buffer)
        wl_buffer_destroy(window.wl.decorations.buffer);

    if (window.wl.native)
        wl_egl_window_destroy(window.wl.native);

    if (window.wl.shellSurface)
        wl_shell_surface_destroy(window.wl.shellSurface);

    if (window.wl.xdg.toplevel)
        xdg_toplevel_destroy(window.wl.xdg.toplevel);

    if (window.wl.xdg.surface)
        xdg_surface_destroy(window.wl.xdg.surface);

    if (window.wl.surface)
        wl_surface_destroy(window.wl.surface);

    free(window.wl.title);
    free(window.wl.monitors);
}

void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const(char)* title) {
    if (window.wl.title)
        free(window.wl.title);
    window.wl.title = _glfw_strdup(title);
    if (window.wl.xdg.toplevel)
        xdg_toplevel_set_title(window.wl.xdg.toplevel, title);
    else if (window.wl.shellSurface)
        wl_shell_surface_set_title(window.wl.shellSurface, title);
}

void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const(GLFWimage)* images) {
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Setting window icon not supported");
}

void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) {
    // A Wayland client is not aware of its position, so just warn and leave it
    // as (0, 0)

    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Window position retrieval not supported");
}

void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) {
    // A Wayland client can not set its position, so just warn

    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Window position setting not supported");
}

void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) {
    if (width)
        *width = window.wl.width;
    if (height)
        *height = window.wl.height;
}

void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) {
    window.wl.width = width;
    window.wl.height = height;
    resizeWindow(window);
}

void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) {
    if (_glfw.wl.wmBase)
    {
        if (window.wl.xdg.toplevel)
        {
            if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
                minwidth = minheight = 0;
            if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
                maxwidth = maxheight = 0;
            xdg_toplevel_set_min_size(window.wl.xdg.toplevel, minwidth, minheight);
            xdg_toplevel_set_max_size(window.wl.xdg.toplevel, maxwidth, maxheight);
            wl_surface_commit(window.wl.surface);
        }
    }
    else
    {
        // TODO: find out how to trigger a resize.
        // The actual limits are checked in the wl_shell_surface::configure handler.
    }
}

void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) {
    // TODO: find out how to trigger a resize.
    // The actual limits are checked in the wl_shell_surface::configure handler.
}

void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) {
    _glfwPlatformGetWindowSize(window, width, height);
    *width *= window.wl.scale;
    *height *= window.wl.scale;
}

void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) {
    if (window.decorated && !window.monitor && !window.wl.decorations.serverSide)
    {
        if (top)
            *top = _GLFW_DECORATION_TOP;
        if (left)
            *left = _GLFW_DECORATION_WIDTH;
        if (right)
            *right = _GLFW_DECORATION_WIDTH;
        if (bottom)
            *bottom = _GLFW_DECORATION_WIDTH;
    }
}

void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale) {
    if (xscale)
        *xscale = cast(float) window.wl.scale;
    if (yscale)
        *yscale = cast(float) window.wl.scale;
}

void _glfwPlatformIconifyWindow(_GLFWwindow* window) {
    if (_glfw.wl.wmBase)
    {
        if (window.wl.xdg.toplevel)
            xdg_toplevel_set_minimized(window.wl.xdg.toplevel);
    }
    else
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Iconify window not supported on wl_shell");
    }
}

void _glfwPlatformRestoreWindow(_GLFWwindow* window) {
    if (window.wl.xdg.toplevel)
    {
        if (window.monitor)
            xdg_toplevel_unset_fullscreen(window.wl.xdg.toplevel);
        if (window.wl.maximized)
            xdg_toplevel_unset_maximized(window.wl.xdg.toplevel);
        // There is no way to unset minimized, or even to know if we are
        // minimized, so there is nothing to do here.
    }
    else if (window.wl.shellSurface)
    {
        if (window.monitor || window.wl.maximized)
            wl_shell_surface_set_toplevel(window.wl.shellSurface);
    }
    _glfwInputWindowMonitor(window, null);
    window.wl.maximized = GLFW_FALSE;
}

void _glfwPlatformMaximizeWindow(_GLFWwindow* window) {
    if (window.wl.xdg.toplevel)
    {
        xdg_toplevel_set_maximized(window.wl.xdg.toplevel);
    }
    else if (window.wl.shellSurface)
    {
        // Let the compositor select the best output.
        wl_shell_surface_set_maximized(window.wl.shellSurface, null);
    }
    window.wl.maximized = GLFW_TRUE;
}

void _glfwPlatformShowWindow(_GLFWwindow* window) {
    if (!window.wl.visible)
    {
        if (_glfw.wl.wmBase)
            createXdgSurface(window);
        else if (!window.wl.shellSurface)
            createShellSurface(window);
        window.wl.visible = GLFW_TRUE;
    }
}

void _glfwPlatformHideWindow(_GLFWwindow* window) {
    if (window.wl.xdg.toplevel)
    {
        xdg_toplevel_destroy(window.wl.xdg.toplevel);
        xdg_surface_destroy(window.wl.xdg.surface);
        window.wl.xdg.toplevel = null;
        window.wl.xdg.surface = null;
    }
    else if (window.wl.shellSurface)
    {
        wl_shell_surface_destroy(window.wl.shellSurface);
        window.wl.shellSurface = null;
    }
    window.wl.visible = GLFW_FALSE;
}

void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) {
    // TODO
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Window attention request not implemented yet");
}

void _glfwPlatformFocusWindow(_GLFWwindow* window) {
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Focusing a window requires user interaction");
}

void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate) {
    if (monitor)
    {
        setFullscreen(window, monitor, refreshRate);
    }
    else
    {
        if (window.wl.xdg.toplevel)
            xdg_toplevel_unset_fullscreen(window.wl.xdg.toplevel);
        else if (window.wl.shellSurface)
            wl_shell_surface_set_toplevel(window.wl.shellSurface);
        setIdleInhibitor(window, GLFW_FALSE);
        if (!_glfw.wl.decorationManager)
            createDecorations(window);
    }
    _glfwInputWindowMonitor(window, monitor);
}

int _glfwPlatformWindowFocused(_GLFWwindow* window) {
    return _glfw.wl.keyboardFocus == window;
}

int _glfwPlatformWindowIconified(_GLFWwindow* window) {
    // wl_shell doesn't have any iconified concept, and xdg-shell doesn’t give
    // any way to request whether a surface is iconified.
    return GLFW_FALSE;
}

int _glfwPlatformWindowVisible(_GLFWwindow* window) {
    return window.wl.visible;
}

int _glfwPlatformWindowMaximized(_GLFWwindow* window) {
    return window.wl.maximized;
}

int _glfwPlatformWindowHovered(_GLFWwindow* window) {
    return window.wl.hovered;
}

int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) {
    return window.wl.transparent;
}

void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) {
    // TODO
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Window attribute setting not implemented yet");
}

void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) {
    if (!window.monitor)
    {
        if (enabled)
            createDecorations(window);
        else
            destroyDecorations(window);
    }
}

void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) {
    // TODO
    _glfwInputError(GLFW_PLATFORM_ERROR,
                    "Wayland: Window attribute setting not implemented yet");
}

float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) {
    return 1.0f;
}

void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) {
}

void _glfwPlatformSetRawMouseMotion(_GLFWwindow* window, GLFWbool enabled) {
    // This is handled in relativePointerHandleRelativeMotion
}

GLFWbool _glfwPlatformRawMouseMotionSupported() {
    return GLFW_TRUE;
}

void _glfwPlatformPollEvents() {
    handleEvents(0);
}

void _glfwPlatformWaitEvents() {
    handleEvents(-1);
}

void _glfwPlatformWaitEventsTimeout(double timeout) {
    handleEvents(cast(int) (timeout * 1e3));
}

void _glfwPlatformPostEmptyEvent() {
    wl_display_sync(_glfw.wl.display);
}

void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) {
    if (xpos)
        *xpos = window.wl.cursorPosX;
    if (ypos)
        *ypos = window.wl.cursorPosY;
}

private GLFWbool isPointerLocked(_GLFWwindow* window);

void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) {
    if (isPointerLocked(window))
    {
        zwp_locked_pointer_v1_set_cursor_position_hint(
            window.wl.pointerLock.lockedPointer,
            wl_fixed_from_double(x), wl_fixed_from_double(y));
        wl_surface_commit(window.wl.surface);
    }
}

void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) {
    _glfwPlatformSetCursor(window, window.wl.currentCursor);
}

const(char)* _glfwPlatformGetScancodeName(int scancode) {
    // TODO
    return null;
}

int _glfwPlatformGetKeyScancode(int key) {
    return _glfw.wl.scancodes[key];
}

int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const(GLFWimage)* image, int xhot, int yhot) {
    cursor.wl.buffer = createShmBuffer(image);
    if (!cursor.wl.buffer)
        return GLFW_FALSE;

    cursor.wl.width = image.width;
    cursor.wl.height = image.height;
    cursor.wl.xhot = xhot;
    cursor.wl.yhot = yhot;
    return GLFW_TRUE;
}

int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) {
    wl_cursor* standardCursor;

    standardCursor = _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorTheme,
                                                translateCursorShape(shape));
    if (!standardCursor)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Standard cursor \"%s\" not found",
                        translateCursorShape(shape));
        return GLFW_FALSE;
    }

    cursor.wl.cursor = standardCursor;
    cursor.wl.currentImage = 0;

    if (_glfw.wl.cursorThemeHiDPI)
    {
        standardCursor = _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorThemeHiDPI,
                                                    translateCursorShape(shape));
        cursor.wl.cursorHiDPI = standardCursor;
    }

    return GLFW_TRUE;
}

void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) {
    // If it's a standard cursor we don't need to do anything here
    if (cursor.wl.cursor)
        return;

    if (cursor.wl.buffer)
        wl_buffer_destroy(cursor.wl.buffer);
}

private void relativePointerHandleRelativeMotion(void* data, zwp_relative_pointer_v1* pointer, uint timeHi, uint timeLo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dxUnaccel, wl_fixed_t dyUnaccel) {
    _GLFWwindow* window = data;
    double xpos = window.virtualCursorPosX;
    double ypos = window.virtualCursorPosY;

    if (window.cursorMode != GLFW_CURSOR_DISABLED)
        return;

    if (window.rawMouseMotion)
    {
        xpos += wl_fixed_to_double(dxUnaccel);
        ypos += wl_fixed_to_double(dyUnaccel);
    }
    else
    {
        xpos += wl_fixed_to_double(dx);
        ypos += wl_fixed_to_double(dy);
    }

    _glfwInputCursorPos(window, xpos, ypos);
}

private const(zwp_relative_pointer_v1_listener) relativePointerListener = zwp_relative_pointer_v1_listener(
    &relativePointerHandleRelativeMotion
);

private void lockedPointerHandleLocked(void* data, zwp_locked_pointer_v1* lockedPointer) {
}

private void unlockPointer(_GLFWwindow* window) {
    zwp_relative_pointer_v1* relativePointer = window.wl.pointerLock.relativePointer;
    zwp_locked_pointer_v1* lockedPointer = window.wl.pointerLock.lockedPointer;

    zwp_relative_pointer_v1_destroy(relativePointer);
    zwp_locked_pointer_v1_destroy(lockedPointer);

    window.wl.pointerLock.relativePointer = null;
    window.wl.pointerLock.lockedPointer = null;
}

private void lockPointer(_GLFWwindow* window);

private void lockedPointerHandleUnlocked(void* data, zwp_locked_pointer_v1* lockedPointer) {
}

private const(zwp_locked_pointer_v1_listener) lockedPointerListener = zwp_locked_pointer_v1_listener(
    &lockedPointerHandleLocked,
    &lockedPointerHandleUnlocked
);

private void lockPointer(_GLFWwindow* window) {
    zwp_relative_pointer_v1* relativePointer;
    zwp_locked_pointer_v1* lockedPointer;

    if (!_glfw.wl.relativePointerManager)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: no relative pointer manager");
        return;
    }

    relativePointer =
        zwp_relative_pointer_manager_v1_get_relative_pointer(
            _glfw.wl.relativePointerManager,
            _glfw.wl.pointer);
    zwp_relative_pointer_v1_add_listener(relativePointer,
                                         &relativePointerListener,
                                         window);

    lockedPointer =
        zwp_pointer_constraints_v1_lock_pointer(
            _glfw.wl.pointerConstraints,
            window.wl.surface,
            _glfw.wl.pointer,
            null,
            ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
    zwp_locked_pointer_v1_add_listener(lockedPointer,
                                       &lockedPointerListener,
                                       window);

    window.wl.pointerLock.relativePointer = relativePointer;
    window.wl.pointerLock.lockedPointer = lockedPointer;

    wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
                          null, 0, 0);
}

private GLFWbool isPointerLocked(_GLFWwindow* window) {
    return window.wl.pointerLock.lockedPointer != null;
}

void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) {
    wl_cursor* defaultCursor;
    wl_cursor* defaultCursorHiDPI = null;

    if (!_glfw.wl.pointer)
        return;

    window.wl.currentCursor = cursor;

    // If we're not in the correct window just save the cursor
    // the next time the pointer enters the window the cursor will change
    if (window != _glfw.wl.pointerFocus || window.wl.decorations.focus != mainWindow)
        return;

    // Unlock possible pointer lock if no longer disabled.
    if (window.cursorMode != GLFW_CURSOR_DISABLED && isPointerLocked(window))
        unlockPointer(window);

    if (window.cursorMode == GLFW_CURSOR_NORMAL)
    {
        if (cursor)
            setCursorImage(window, &cursor.wl);
        else
        {
            defaultCursor = _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorTheme,
                                                       "left_ptr");
            if (!defaultCursor)
            {
                _glfwInputError(GLFW_PLATFORM_ERROR,
                                "Wayland: Standard cursor not found");
                return;
            }
            if (_glfw.wl.cursorThemeHiDPI)
                defaultCursorHiDPI =
                    _glfw.wl.cursor.theme_get_cursor(_glfw.wl.cursorThemeHiDPI,
                                               "left_ptr");
            _GLFWcursorWayland cursorWayland = [
                defaultCursor,
                defaultCursorHiDPI,
                null,
                0, 0,
                0, 0,
                0
            ];
            setCursorImage(window, &cursorWayland);
        }
    }
    else if (window.cursorMode == GLFW_CURSOR_DISABLED)
    {
        if (!isPointerLocked(window))
            lockPointer(window);
    }
    else if (window.cursorMode == GLFW_CURSOR_HIDDEN)
    {
        wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial, null, 0, 0);
    }
}

private void dataSourceHandleTarget(void* data, wl_data_source* dataSource, const(char)* mimeType) {
    if (_glfw.wl.dataSource != dataSource)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Unknown clipboard data source");
        return;
    }
}

private void dataSourceHandleSend(void* data, wl_data_source* dataSource, const(char)* mimeType, int fd) {
    const(char)* string = _glfw.wl.clipboardSendString;
    size_t len = _glfw.wl.clipboardSendSize;
    int ret;

    if (_glfw.wl.dataSource != dataSource)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Unknown clipboard data source");
        return;
    }

    if (!string)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Copy requested from an invalid string");
        return;
    }

    if (strcmp(mimeType, "text/plain;charset=utf-8") != 0)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Wrong MIME type asked from clipboard");
        close(fd);
        return;
    }

    while (len > 0)
    {
        ret = write(fd, string, len);
        if (ret == -1 && errno == EINTR)
            continue;
        if (ret == -1)
        {
            // TODO: also report errno maybe.
            _glfwInputError(GLFW_PLATFORM_ERROR,
                            "Wayland: Error while writing the clipboard");
            close(fd);
            return;
        }
        len -= ret;
    }
    close(fd);
}

private void dataSourceHandleCancelled(void* data, wl_data_source* dataSource) {
    wl_data_source_destroy(dataSource);

    if (_glfw.wl.dataSource != dataSource)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Unknown clipboard data source");
        return;
    }

    _glfw.wl.dataSource = null;
}

private const(wl_data_source_listener) dataSourceListener = wl_data_source_listener(
    &dataSourceHandleTarget,
    &dataSourceHandleSend,
    &dataSourceHandleCancelled,
);

void _glfwPlatformSetClipboardString(const(char)* string) {
    if (_glfw.wl.dataSource)
    {
        wl_data_source_destroy(_glfw.wl.dataSource);
        _glfw.wl.dataSource = null;
    }

    if (_glfw.wl.clipboardSendString)
    {
        free(_glfw.wl.clipboardSendString);
        _glfw.wl.clipboardSendString = null;
    }

    _glfw.wl.clipboardSendString = strdup(string);
    if (!_glfw.wl.clipboardSendString)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Impossible to allocate clipboard string");
        return;
    }
    _glfw.wl.clipboardSendSize = strlen(string);
    _glfw.wl.dataSource =
        wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
    if (!_glfw.wl.dataSource)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Impossible to create clipboard source");
        free(_glfw.wl.clipboardSendString);
        return;
    }
    wl_data_source_add_listener(_glfw.wl.dataSource,
                                &dataSourceListener,
                                null);
    wl_data_source_offer(_glfw.wl.dataSource, "text/plain;charset=utf-8");
    wl_data_device_set_selection(_glfw.wl.dataDevice,
                                 _glfw.wl.dataSource,
                                 _glfw.wl.serial);
}

private GLFWbool growClipboardString() {
    char* clipboard = _glfw.wl.clipboardString;

    clipboard = realloc(clipboard, _glfw.wl.clipboardSize * 2);
    if (!clipboard)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Impossible to grow clipboard string");
        return GLFW_FALSE;
    }
    _glfw.wl.clipboardString = clipboard;
    _glfw.wl.clipboardSize = _glfw.wl.clipboardSize * 2;
    return GLFW_TRUE;
}

const(char)* _glfwPlatformGetClipboardString() {
    int[2] fds;
    int ret;
    size_t len = 0;

    if (!_glfw.wl.dataOffer)
    {
        _glfwInputError(GLFW_FORMAT_UNAVAILABLE,
                        "No clipboard data has been sent yet");
        return null;
    }

    ret = pipe2(fds.ptr, O_CLOEXEC);
    if (ret < 0)
    {
        // TODO: also report errno maybe?
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Impossible to create clipboard pipe fds");
        return null;
    }

    wl_data_offer_receive(_glfw.wl.dataOffer, "text/plain;charset=utf-8", fds[1]);
    close(fds[1]);

    // XXX: this is a huge hack, this function shouldn’t be synchronous!
    handleEvents(-1);

    while (1)
    {
        // Grow the clipboard if we need to paste something bigger, there is no
        // shrink operation yet.
        if (len + 4096 > _glfw.wl.clipboardSize)
        {
            if (!growClipboardString())
            {
                close(fds[0]);
                return null;
            }
        }

        // Then read from the fd to the clipboard, handling all known errors.
        ret = read(fds[0], _glfw.wl.clipboardString + len, 4096);
        if (ret == 0)
            break;
        if (ret == -1 && errno == EINTR)
            continue;
        if (ret == -1)
        {
            // TODO: also report errno maybe.
            _glfwInputError(GLFW_PLATFORM_ERROR,
                            "Wayland: Impossible to read from clipboard fd");
            close(fds[0]);
            return null;
        }
        len += ret;
    }
    close(fds[0]);
    if (len + 1 > _glfw.wl.clipboardSize)
    {
        if (!growClipboardString())
            return null;
    }
    _glfw.wl.clipboardString[len] = '\0';
    return _glfw.wl.clipboardString;
}

void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) {
    if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)
        return;

    extensions[0] = "VK_KHR_surface";
    extensions[1] = "VK_KHR_wayland_surface";
}

int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint queuefamily) {
    PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = cast(PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)
        vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceWaylandPresentationSupportKHR");
    if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR)
    {
        _glfwInputError(GLFW_API_UNAVAILABLE,
                        "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension");
        return VK_NULL_HANDLE;
    }

    return vkGetPhysicalDeviceWaylandPresentationSupportKHR(device,
                                                            queuefamily,
                                                            _glfw.wl.display);
}

VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const(VkAllocationCallbacks)* allocator, VkSurfaceKHR* surface) {
    VkResult err;
    VkWaylandSurfaceCreateInfoKHR sci;
    PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;

    vkCreateWaylandSurfaceKHR = cast(PFN_vkCreateWaylandSurfaceKHR)
        vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR");
    if (!vkCreateWaylandSurfaceKHR)
    {
        _glfwInputError(GLFW_API_UNAVAILABLE,
                        "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension");
        return VkResult.VK_ERROR_EXTENSION_NOT_PRESENT;
    }

    memset(&sci, 0, sci.sizeof);
    sci.sType = VkStructureType.VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
    sci.display = _glfw.wl.display;
    sci.surface = window.wl.surface;

    err = vkCreateWaylandSurfaceKHR(instance, &sci, allocator, surface);
    if (err)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Wayland: Failed to create Vulkan surface: %s",
                        _glfwGetVulkanResultString(err));
    }

    return err;
}


//////////////////////////////////////////////////////////////////////////
//////                        GLFW native API                       //////
//////////////////////////////////////////////////////////////////////////

wl_display* glfwGetWaylandDisplay() {
    mixin(_GLFW_REQUIRE_INIT_OR_RETURN!"null");
    return _glfw.wl.display;
}

wl_surface* glfwGetWaylandWindow(GLFWwindow* handle) {
    _GLFWwindow* window = cast(_GLFWwindow*) handle;
    mixin(_GLFW_REQUIRE_INIT_OR_RETURN!"null");
    return window.wl.surface;
}
